package controller;

import java.awt.Component;
import java.util.Vector;

import javax.swing.JOptionPane;

import model.Cliente;
import model.Conjunto;
import model.Factura;
import model.ItemMaterial;
import model.Material;
import model.OrdenCompra;
import model.PrendaSinTemporada;
import model.PrendaTemporada;
import model.Producto;
import model.Proveedor;
import model.SistemaProveedores;
import persistencia.AdministradorPersistenciaClientes;
import persistencia.AdministradorPersistenciaConjunto;
import persistencia.AdministradorPersistenciaFactura;
import persistencia.AdministradorPersistenciaMateriales;
import persistencia.AdministradorPersistenciaOrdenCompra;
import persistencia.AdministradorPersistenciaPrendaTemporada;
import persistencia.AdministradorPersistenciaProducto;
import persistencia.AdministradorPersistenciaProveedores;
import persistencia.AdministradorPersistenciaSinTemporada;
import view.ClienteView;
import view.ItemFacturaView;
import view.ItemMaterialView;
import view.MaterialView;
import view.ProductoView;
import view.ProveedorView;

public class Sistema {
	
	/* Atributos */
	private static Sistema instancia;
	private Vector<Factura> facturas;
	private Vector<Cliente> clientes;
	private Vector<Proveedor> proveedores;
	private Vector<Producto> productos;
	private Vector<Material> materiales;
	private Vector<OrdenCompra> ordenesCompras;
	protected static final Component IngresarCant = null;
	
	/* Constructores */
	private Sistema() throws Exception{
		this.facturas = new Vector<Factura>();
		this.clientes = AdministradorPersistenciaClientes.getInstancia().obtenerClientes();
		this.proveedores = AdministradorPersistenciaProveedores.getInstancia().obtenerProveedores();
		this.materiales = new Vector<Material>();
		this.ordenesCompras = new Vector<OrdenCompra>();
		this.productos = new Vector<Producto>();
	}
		
	public static Sistema getInstance() throws Exception{
		if (instancia == null){
			instancia = new Sistema();
		}
		return instancia;
	}
	
	// Geters y Seters 

	public static Sistema getInstancia() {
		return instancia;
	}

	public Vector<Factura> getFacturas() {
		return facturas;
	}

	public Vector<Cliente> getClientes() {
		return clientes;
	}

	public Vector<Proveedor> getProveedores() {
		return proveedores;
	}

	public Vector<Producto> getProductos() {
		return productos;
	}

	public Vector<Material> getMateriales() {
		return materiales;
	}

	public Vector<OrdenCompra> getOrdenesCompras() {
		return ordenesCompras;
	}
	
	/* Metodos de clase */
	
	/********************************************************************/
	/***************** INICIO PROVEEDORES & CLIENTES ********************/
	/********************************************************************/
	
	public Proveedor buscarProveedor(long cuit) throws Exception {
		Proveedor proveedor = null;
		for (Proveedor p : this.proveedores){
			if (p.getCuit() == cuit){
				proveedor = p;
			}
		}
		if (proveedor == null){
			proveedor = AdministradorPersistenciaProveedores.getInstancia().obtenerProveedor(cuit);
			if (proveedor != null)
				this.proveedores.add(proveedor);
		}
		return proveedor;
	}

	public Cliente buscarCliente(int dni) throws Exception {
		Cliente cliente = null;
		for (Cliente c : this.clientes){
			if (c.getDni() == dni)
				cliente = c;
		}
		if (cliente == null){
			cliente = AdministradorPersistenciaClientes.getInstancia().obtenerCliente(dni);
			if(cliente != null)
				this.clientes.add(cliente);
		}
		return cliente;
	}
	
	/********************************************************************/
	/******************* FIN PROVEEDORES & CLIENTES *********************/
	/********************************************************************/
	
	/********************************************************************/
	/************************ INICIO MATERIALES *************************/
	/********************************************************************/
	
	public boolean insertarMaterial(String nombre, int stock, int ptoPed, 
			float costo, long cuit) throws Exception {
		boolean resInsertar = false;
		if (!existeMaterial(nombre)){
			// No esta ni en memoria ni en la base, lo inserta
			Proveedor proveedor = buscarProveedor(cuit);
			int codigo = AdministradorPersistenciaMateriales.getInstance().insertarMaterial(nombre,stock,ptoPed,costo,cuit);
			Material material = new Material(codigo,nombre,stock,ptoPed,costo,proveedor);
			this.materiales.add(material);
			resInsertar = true;
		}
		return resInsertar;
	}
	
	public void actualizarMaterial(int codigo, String nombre, int stock, int ptoPed, 
			float costo, long cuit) throws Exception {
		// El material es seguro que existe, porque viene de un ComboBox que consulta a la base.
		// Primero lo buscamos en memoria y si se encuentra lo actualizamos.
		for (Material m: this.materiales){
			if (m.getCodigo() == codigo){
				m.setNombre(nombre);
				m.setCantidadStock(stock);
				m.setPuntoPedido(ptoPed);
				m.setCosto(costo);
				if (!(m.getProveedor().getCuit() == cuit)){
					Proveedor proveedor = buscarProveedor(cuit);
					m.setProveedor(proveedor);
				}
			}
		}
		AdministradorPersistenciaMateriales.getInstance().actualizarMaterial(codigo,nombre,stock,ptoPed,costo,cuit);
	}
	
	
	public boolean eliminarMaterial(int codigo) throws Exception {
		/*
		 *  Si la ejecucion llego hasta aca, el material existe.
		 *  
		 *  Primero veo que no este vinculado con ningun itemMaterial
		 *  Si no lo esta, lo elimino del cache.
		 *  Por ultimo, pido la baja en la base de datos.
		 *  
		 *  Devuelve verdadero si lo pudo eliminar
		 *  Caso contrario devuelve falso
		 */
		for (Producto p: this.productos){
			if (validarPertenencia(codigo,p))
					return false;
		}
		for (Material m : this.materiales){
			if (m.getCodigo() == codigo){
				this.materiales.remove(m);
			}
		}
		 return AdministradorPersistenciaMateriales.getInstance().eliminarMaterial(codigo);
	}
	
	private boolean validarPertenencia (int codigo, Producto p){
		/*
		 * Devuelve verdadero si el codigo es parte de algun Producto
		 * Caso contrario devuelve falso
		 */
		if (p.getTipo().equalsIgnoreCase("PRENDA_CON_TEMPORADA")){
			PrendaTemporada prenda = (PrendaTemporada) p;
			for(ItemMaterial i: prenda.getMateriales()){
				if (i.getMaterial().getCodigo() == codigo)
					return true;
			}
		}else if(p.getTipo().equalsIgnoreCase("PRENDA_SIN_TEMPORADA")){
			PrendaSinTemporada prenda = (PrendaSinTemporada) p;
			for(ItemMaterial i: prenda.getMateriales()){
				if (i.getMaterial().getCodigo() == codigo)
					return true;
			}
		}else{
			Conjunto conjunto = (Conjunto) p;
			for (Producto e: conjunto.getProductos()){
				return validarPertenencia(codigo,e);
			}
		}
		return false;
	}
	
	public Material buscarMaterial(int codigo) throws Exception{
		Material material = null;
		for (Material m : this.materiales){
			if (m.getCodigo() == codigo){
				material = m;
			}
		}
		if (material == null){
			material = AdministradorPersistenciaMateriales.getInstance().obtenerMaterial(codigo);
			if (material != null)
				this.materiales.add(material);
		}
		return material;
	}
	
	private boolean existeMaterial(String nombre) throws Exception{
		for (Material m: this.materiales){
			if (m.getNombre() == nombre){
				return true;
			}
			
		}
		return AdministradorPersistenciaMateriales.getInstance().existeNombreMaterial(nombre);
	}
	
	
	public void actualizarStockMaterial(int codigo, int cantidad) throws Exception {
		if (buscarMaterial(codigo) != null){
			for (Material m : this.materiales){
				if (m.getCodigo()==codigo){
					m.setCantidadStock(m.getCantidadStock()-cantidad);
				}
			}
		}
		boolean stockBajo=false;
		stockBajo = AdministradorPersistenciaMateriales.getInstance().actualizarStockMaterial(codigo,cantidad);
		if (stockBajo == true) {
			int opcion = JOptionPane.showOptionDialog(null, "El stock del Material "+codigo+" esta por debajo de su punto de pedido. Desea generar Orden de Compra?", "Generar OC", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, null, JOptionPane.YES_OPTION);
			if (opcion == 0){
				String nuevaCant = JOptionPane.showInputDialog(IngresarCant, "Ingrese la cantidad a pedir de "+codigo+":");
				this.generarOrdenCompra(codigo,Float.parseFloat(nuevaCant));
			}
		}
	}
	
	

	/********************************************************************/
	/*************************** FIN MATERIALES *************************/
	/********************************************************************/
	
	/********************************************************************/
	/************************** INICIO PRENDAS **************************/
	/********************************************************************/
	
	public int insertarPrendaSinTemporada(String nombre, int stock, Vector<ItemMaterialView> items) throws Exception {
		int codigo = 0;
		if (!existeProducto(nombre,"PRENDA_SIN_TEMPORADA")){
			PrendaSinTemporada nueva = new PrendaSinTemporada(nombre,stock);
			nueva.calcularPrecioPrenda(items);
			codigo = AdministradorPersistenciaSinTemporada.getInstance().insertarPrenda(nombre,stock,nueva.getPrecio(),items);
			nueva.setCodigo(codigo);
			for (ItemMaterialView i: items){
				Material m = buscarMaterial(i.getCodigoMaterial());
				nueva.crearItemMaterial(m, i.getCantidad());
				actualizarStockMaterial(i.getCodigoMaterial(),i.getCantidad()*stock);
			}
			this.productos.add(nueva);
		}
		return codigo;
	}

	

	public int insertarPrendaTemporada(String nombre,String estacion, int stock, Vector<ItemMaterialView> items) throws Exception {
		int codigo = 0;
		if (!existeProducto(nombre,"PRENDA_CON_TEMPORADA")){
			PrendaTemporada nueva = new PrendaTemporada(nombre,estacion,stock);
			nueva.calcularPrecioPrenda(items);
			codigo = AdministradorPersistenciaPrendaTemporada.getInstance().insertarPrenda(nombre,estacion,stock,nueva.getPrecio(),items);
			nueva.setCodigo(codigo);
			for (ItemMaterialView i: items){
				Material m = buscarMaterial(i.getCodigoMaterial());
				nueva.crearItemMaterial(m, i.getCantidad());
				actualizarStockMaterial(i.getCodigoMaterial(),i.getCantidad()*stock);
			}
			this.productos.add(nueva);
			
		}
		return codigo;
	}
	
	public boolean actualizarPrendaSinTemporada(int codigo, String nombre,int stock, Vector<ItemMaterialView> items) throws Exception {
		// Lo borro del cache si ya estaba cargado
		for (Producto p : this.productos){
			if (p.getCodigo() == codigo){
				this.productos.remove(p);
			}
		}
		// Creo la prenda y sus items
		PrendaSinTemporada nueva = new PrendaSinTemporada(codigo,nombre,stock);
		nueva.calcularPrecioPrenda(items);
		for (ItemMaterialView i: items){
			Material m = buscarMaterial(i.getCodigoMaterial());
			nueva.crearItemMaterial(m, i.getCantidad());
			actualizarStockMaterial(i.getCodigoMaterial(),i.getCantidad()*stock);
		}
		// Agrego la prenda al cache y la base
		this.productos.add(nueva);
		return AdministradorPersistenciaSinTemporada.getInstance().actualizarPrenda(nueva);
	}

	public boolean actualizarPrendaTemporada(int codigo, String nombre, String estacion, int stock, Vector<ItemMaterialView> items) throws Exception {
		// Lo borro del cache si ya estaba cargado
		for (Producto p : this.productos){
			if (p.getCodigo() == codigo){
				this.productos.remove(p);
			}
		}
		// Creo la prenda y sus items
		PrendaTemporada nueva = new PrendaTemporada(codigo,nombre,estacion,stock);
		nueva.calcularPrecioPrenda(items);
		for (ItemMaterialView i: items){
			Material m = buscarMaterial(i.getCodigoMaterial());
			nueva.crearItemMaterial(m, i.getCantidad());
			actualizarStockMaterial(i.getCodigoMaterial(),i.getCantidad()*stock);
		}
		// Agrego la prenda al cache y la base
		this.productos.add(nueva);
		return AdministradorPersistenciaPrendaTemporada.getInstance().actualizarPrenda(nueva);
	}
	
	public boolean eliminarPrendaSinTemporada(int codigo) throws Exception {
		for (Producto p : this.productos){
			if (p.getCodigo() == codigo){
				this.productos.remove(p);
			}
		}
		// Una vez eliminado de la memoria cache, hago la baja logica en la base:
		return AdministradorPersistenciaSinTemporada.getInstance().eliminarPrenda(codigo);
	}

	
	public boolean eliminarPrendaTemporada(int codigo) throws Exception {
		for (Producto p : this.productos){
			if (p.getCodigo() == codigo){
				this.productos.remove(p);
			}
		}
		// Una vez eliminado de la memoria cache, hago la baja logica en la base:
		return AdministradorPersistenciaPrendaTemporada.getInstance().eliminarPrenda(codigo);
	}
	
	private Boolean existeProducto(String nombre, String tipo) throws Exception {
		/*
		 * Devuelve verdadero si el producto existe en cache o en la base
		 * Caso contrario devuelve falso
		 */
		boolean existeProd = false;
		for (Producto p: this.productos){
			if (p.getNombre() == nombre && p.getTipo().compareToIgnoreCase(tipo) == 0)
				existeProd = true;
		}
		if(!existeProd){
			existeProd = AdministradorPersistenciaProducto.getInstance().existeProducto(nombre,tipo);
		}
		return existeProd;
	}
	
	public Producto buscarProducto(int codigo) throws Exception{
		Producto producto = null;
		for (Producto p: this.productos){
			if (p.getCodigo() == codigo){
				producto = p;
			}
		}
		if (producto == null){
			producto = AdministradorPersistenciaProducto.getInstance().buscarProducto(codigo);
			if (producto != null){
				this.productos.add(producto);
			}
		}
		return producto;
	}
	
	
	
	/********************************************************************/
	/**************************** FIN PRENDAS ***************************/
	/********************************************************************/
	
	/********************************************************************/
	/************************* INICIO CONJUNTOS *************************/
	/********************************************************************/
		
	public boolean altaConjunto(String nombre, float descuento, int stock, Vector<String> codigos) throws Exception {
		boolean rc = false;
		if (!existeProducto(nombre,"CONJUNTO")){
			Conjunto c = new Conjunto(nombre,descuento,stock);
			for (String s: codigos){
				Producto p = buscarProducto(Integer.valueOf(s));
				c.agregarProducto(p);
			}
			float precio = c.calcularPrecio();
			int codigo = AdministradorPersistenciaConjunto.getInstance().insertarConjunto(nombre, descuento, stock, precio, codigos);
			c.setCodigo(codigo);
			productos.add(c);
			rc = true;
		}
		return rc;
	}

	public boolean actualizarConjunto(int codigo, String nombre, float descuento, Vector<String> codigos) throws Exception {
		Conjunto c = (Conjunto) buscarProducto(codigo);
		c.setNombre(nombre);
		c.setDescuento(descuento);
		c.getProductos().removeAllElements();
		for(String codigoProducto : codigos){
			Producto p = buscarProducto(Integer.valueOf(codigoProducto));
			c.agregarProducto(p);
		}
		c.calcularPrecio();
		return AdministradorPersistenciaConjunto.getInstance().actualizarConjunto(c);
	}
	
	public boolean eliminarConjunto(int codigo){
		for(Producto p : productos){
			if(p.getCodigo() == codigo){
				productos.remove(p);
			}
		}
		return AdministradorPersistenciaConjunto.getInstance().eliminarConjunto(codigo);
	}
	
	/********************************************************************/
	/*************************** FIN CONJUNTOS **************************/
	/********************************************************************/
	
	/********************************************************************/
	/************************ INICIO FACTURACION ************************/
	/********************************************************************/
	
	public float insertarVenta(int dni, Vector<ItemFacturaView> productos) throws Exception {
		Cliente cliente = buscarCliente(dni);
		Factura nueva = new Factura(cliente);
		for (ItemFacturaView i: productos){
			ProductoView p = i.getProductoView();
			if (p.getTipo().equalsIgnoreCase("PRENDA_CON_TEMPORADA")){
				PrendaTemporada prenda = (PrendaTemporada) Sistema.getInstance().buscarProducto(p.getCodigo());
				nueva.crearItemFactura(prenda, i.getCantidad());
			}else if (p.getTipo().equalsIgnoreCase("PRENDA_SIN_TEMPORADA")){
				PrendaSinTemporada prenda = (PrendaSinTemporada) Sistema.getInstance().buscarProducto(p.getCodigo());
				nueva.crearItemFactura(prenda, i.getCantidad());
			}else{
				Conjunto conjunto = (Conjunto) Sistema.getInstance().buscarProducto(p.getCodigo());
				nueva.crearItemFactura(conjunto, i.getCantidad());
			}
			actualizarStockProducto(p.getCodigo(),i.getCantidad());
		}
		nueva.calcularTotalFactura();
		this.facturas.add(nueva);
		AdministradorPersistenciaFactura.getInstancia().insertarFactura(nueva);
		return nueva.getTotal();
	}	
	
	private void generarOrdenCompra(int codigoMat, float nuevaCant) throws Exception {
		OrdenCompra oc = new OrdenCompra(codigoMat,nuevaCant);
		int codigo=AdministradorPersistenciaOrdenCompra.getInstancia().insertarOC(oc);
		oc.setNroOC(codigo);
		oc.getItemsOrdenCompra().get(0).getMaterial().add(new SistemaProveedores());
		oc.getItemsOrdenCompra().get(0).getMaterial().notifyAll(oc);
		this.ordenesCompras.add(oc);
	}
	 

	private void actualizarStockProducto(int codigo, int cant) throws Exception {
		Producto producto = buscarProducto(codigo);
		producto.setStock(producto.getStock()-cant);
		if (producto.getTipo().equalsIgnoreCase("PRENDA_CON_TEMPORADA")){
			PrendaTemporada prenda = (PrendaTemporada) producto;
			AdministradorPersistenciaPrendaTemporada.getInstance().actualizarPrenda(prenda);
		}else if (producto.getTipo().equalsIgnoreCase("PRENDA_SIN_TEMPORADA")){
			PrendaSinTemporada prenda = (PrendaSinTemporada) producto;
			AdministradorPersistenciaSinTemporada.getInstance().actualizarPrenda(prenda);
		}else{
			Conjunto conjunto = (Conjunto) producto;
			AdministradorPersistenciaConjunto.getInstance().actualizarConjunto(conjunto);
		}
		
	}
	
	/********************************************************************/
	/************************** FIN FACTURACION *************************/
	/********************************************************************/
	
	/********************************************************************/
	/******************************* VISTAS *****************************/
	/********************************************************************/
	
	// Este metodo es para llenar el ComboBox de elegir proveedores, en la ventana Alta Material.
	public Vector<ProveedorView> obtenerVistaProveedores() throws Exception {
		/* Usamos el vector con los datos cargados en el inicio del programa*/
		Vector<ProveedorView> lista = new Vector<ProveedorView>();
		for (int i=0; i < proveedores.size(); i++){
			ProveedorView p = new ProveedorView(
					proveedores.elementAt(i).getRazonSocial(),
					proveedores.elementAt(i).getCuit(),
					proveedores.elementAt(i).getTelefono(),
					proveedores.elementAt(i).getEmail()
					);
			lista.add(p);
		}
		return lista;
		/* Si no cargamos todos proveedores al inicio del sistema usamos la base */
		//return AdministradorPersistenciaProveedores.getInstancia().obtenerVistaProveedores();
	}
	
	public Vector<ClienteView> obtenerVistaClientes() {
		Vector<ClienteView> lista = new Vector<ClienteView>();
		for (int i=0; i < clientes.size(); i++){
			ClienteView c = new ClienteView(
					clientes.elementAt(i).getDni(),
					clientes.elementAt(i).getNombre(),
					clientes.elementAt(i).getApellido(),
					clientes.elementAt(i).getTelefono()
					);
			lista.add(c);
		}
		return lista;
		/* Si no cargamos todos los clientes al inicio del sistema usamos la base */
		// return AdministradorPersistenciaClientes.getInstancia().obtenerVistaClientes();
	}
	
	public Vector<ProductoView> obtenerVistaProductos() throws Exception {
		return AdministradorPersistenciaProducto.getInstance().obtenerVistaProductos();
	}
	
	public Vector<ProductoView> obtenerVistaProductosConTemporada() throws Exception {
		return AdministradorPersistenciaPrendaTemporada.getInstance().obtenerVistaProductos();
	}
	
	public Vector<ProductoView> obtenerVistaProductosSinTemporada() throws Exception {
		return AdministradorPersistenciaSinTemporada.getInstance().obtenerVistaProductos();
	}
	
	public Vector<ProductoView> obtenerVistaConjuntos() throws Exception {
		return AdministradorPersistenciaConjunto.getInstance().obtenerVistaConjuntos();
	}
	
	public Vector<MaterialView> obtenerVistaMateriales() throws Exception {
		return AdministradorPersistenciaMateriales.getInstance().obtenerVistaMateriales();
	}
	
	/********************************************************************/
	/***************************** FIN VISTAS ***************************/
	/********************************************************************/

}
